Claude Code 解析

Claude code设计

背景

claude code具体是干什么的就不用跟大家说了:

那我们从工程的角度去拆解他:

其产品本质可以理解为:

CLI/TUI 外壳

-> 输入编译器

-> 多轮 Agent Loop

-> 模型调用适配层

-> 工具执行器

-> Hook/Permission

-> Session/Transcript/Plan/FileHistory

设计进化

我们从chatbot到真正的agent就是有了工具调用!

但是工具调用又会带来一些问题:

  1. 工具注册与发现:谁来管理工具的注册表?如何让模型知道有哪些工具可用?如何在不重启系统的情况下动态添加新工具?
  2. 参数校验:谁来校验工具调用的参数?模型可能传递错误类型的参数、缺少必填字段、或传入超出范围的值。校验逻辑放在哪一层?
  3. 权限管控:谁来决定某个工具调用是否应该被执行?模型可能要求执行 rm -rf /,这显然不应该被允许。但有些操作在特定上下文中是安全的——如何平衡安全性与效率?
  4. 错误恢复:工具执行可能失败,API 调用可能超时,LLM 的输出可能不符合预期格式。每一种错误场景都需要有对应的恢复策略,否则 Agent 会陷入"出错-重试-再出错"的死循环。
  5. 状态一致性:多个工具调用可能操作同一份资源。如何在多轮调用之间维护状态一致性?如何避免"读到过期数据"的问题?
  6. 并发与调度:某些工具调用可以并行执行(如同时读取多个文件),某些必须串行(如先创建目录再写文件)。如何智能地调度这些调用以最大化效率?

这样就引出了我们的Agent Harness的封装

简单封装的思路 是:调用 LLM API,解析输出中的工具调用指令,执行工具,把结果拼回 Prompt,再次调用 LLM API。这是一个典型的 while 循环。

这种思路的问题在于它假设了一个理想化的世界:API 调用不会超时,模型输出永远格式正确,用户不需要实时看到进度,上下文窗口是无限的,所有操作都是安全的。但在真实的生产环境中,每一个假设都会被打破。


Agent Harness 的思路 则需要考虑以下问题:

  1. 流式输出:LLM 的响应是流式的,用户需要实时看到 Agent 的思考过程,而不是等待整个响应完成。如何在不阻塞主线程的情况下实现增量渲染?如果使用回调,会导致"回调地狱";如果使用 Promise 链,会失去中途取消的能力;如果使用事件发射器,会增加内存管理的复杂度。
  2. 权限管控:LLM 可能要求执行 rm -rf /,这显然不应该被允许。权限系统应该在哪一层介入?如果在外层统一拦截,无法处理工具特定的权限逻辑(如 Bash 命令的风险评估);如果在每个工具内部检查,会导致重复代码和权限策略不一致。如何平衡安全性与效率?
  3. 上下文管理:随着对话的进行,上下文窗口会被填满。何时触发上下文压缩?压缩策略如何保证不丢失关键信息?压缩后的上下文如何与缓存系统协同?错误的压缩策略可能导致 Agent "忘记"关键信息,从而做出错误的决策。
  4. 错误恢复:工具执行可能失败,API 调用可能超时,LLM 的输出可能不符合预期格式。每一种错误场景都需要有对应的恢复策略。没有统一的错误恢复框架,每种错误都需要单独处理,代码会迅速膨胀到不可维护的程度。
  5. 状态持久化:用户中断会话后如何恢复?多个子智能体如何共享状态?状态更新如何保证不可变性?如果状态管理混乱,Agent 可能在恢复后产生不一致的行为。
  6. 可扩展性:如何让第三方开发者安全地注册新工具?如何支持 MCP(Model Context Protocol)等外部协议?如果没有清晰的扩展接口,Agent 的能力将永远受限于原始开发者的想象力。

基本设计

claude code的迭代循环:

  1. 构建 API 请求(系统 Prompt + 消息历史 + 工具定义)
  2. 调用 LLM API 并流式接收响应
  3. 解析响应中的工具调用指令
  4. 通过权限管线校验每个工具调用
  5. 执行被允许的工具调用
  6. 将工具结果作为新消息注入历史
  7. 决定是否继续循环(如果 LLM 返回了新的工具调用)或终止(如果 LLM 返回了纯文本回复)

状态管理:

跨迭代状态被封装在一个不可变的 State 对象中,每次迭代时通过整体替换而非逐字段修改。这种不可变状态流转的模式使得状态的每次变化都是可追溯的。

工具管理基石:

工具类型模块定义了 Claude Code 中所有工具必须遵循的类型契约。这是一个典型的"接口即架构"的案例——通过定义清晰的类型接口,工具系统的架构约束被编译器强制执行。

这个类型定义中蕴含了丰富的架构决策:

  • 工具的执行方法接收权限检查回调,意味着权限检查被内嵌到了工具执行流程中,而非外部拦截。
  • 进度回调支持工具执行过程的增量进度报告,这是流式 UI 的基础。
  • 并发安全声明标记工具是否可以并行执行,这影响调度策略。
  • 中断行为定义用户中断时的行为(取消还是阻塞),这是用户体验的关键细节。
  • 破坏性标记标识工具是否执行不可逆操作,这是权限系统的重要输入。

工具注册:

工具注册模块中的核心函数返回所有可用工具的完整列表,是工具系统的"单一事实来源"(Single Source of Truth)。值得注意的设计细节:

  1. 条件注册:某些工具通过 Feature Flag 控制,如 REPL 工具只在内部版本中可用,定时任务工具只在特定功能模式下启用。这使得同一份代码可以服务于不同的产品形态。
  2. 延迟加载:部分工具使用动态导入而非静态导入来避免循环依赖和减少启动时间。
  3. 工具过滤:工具过滤函数在将工具列表发送给 LLM 之前,会根据权限上下文过滤掉被禁止的工具,确保模型甚至无法"看到"它不应该使用的工具。